﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using static System.Runtime.InteropServices.JavaScript.JSType;
using System.Transactions;
using System.Linq.Expressions;
using System.ComponentModel.Design;

namespace TCPlib.ModbusTcp
{
   public class TCPSocket
   {
        /*1.事务处理标识(Transaction Identifier) - 2字节
        用于请求和响应匹配，每次通信递增
        客户端生成，服务器在响应中返回相同值
        
        2.协议标识(Protocol Identifier) - 2字节
        Modbus TCP固定为0x0000
        用于区分其他基于TCP的协议
        
        3.长度(Length) - 2字节
        表示后续部分的字节数（从单元标识开始到报文结束）
        
        4.单元标识(Unit Identifier) - 1字节
        用于串行链路或其它总线上连接的远程从站标识
        TCP/IP通信中通常设为0xFF或从站地址
        
        5.功能码(Function Code) - 1字节
        指示要执行的操作类型，例如：
        0x01: 读线圈
        0x03: 读保持寄存器
        0x05: 写单个线圈
        0x10: 写多个寄存器
        
        6.起始线圈地址(Starting Address) - 2字节
        指定要操作的第一个线圈/寄存器的地址
        Modbus地址从0开始
        线圈长度(Quantity) - 2字节
        要读取或写入的线圈/寄存器数量*/

        private Socket socket;  //创建一个socket对象
        private CancellationTokenSource _cts=new CancellationTokenSource(); //取消令牌
        private object obj;

        public int sendTimeout { get; set; } = 3000; //连接超时时间 
        public int receiveTimeout { get; set; } = 3000; //接收超时时间
        public int SleepTime { get; set; }   // 读取接收间隔时间
        public int MaxWriteTimes { get; set; } = 1000; //读取超时时间设置
        public byte slaveId { get; set; } = 0x01; //从站地址

        /// <summary>
        /// 连接到指定的IP和端口
        /// </summary>
        /// <param name="ip">IP地址</param>
        /// <param name="port">端口号</param>
        /// <returns>是否连接成功</returns>
        public bool Connect(string ip,int port)
        {
            //初始化连接对象
            socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
            socket.SendTimeout = sendTimeout;
            socket.ReceiveTimeout = receiveTimeout;
            try
            {
                if (CheckIP(ip))
                {
                    socket.Connect(ip, port);
                }
                else
                {
                    return false;
                }
            }
            catch (Exception)
            {
                return false;
            }
            return true;
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        public void DisConnect()
        {
            if (socket != null)
            {
                socket.Close();
            }
        }

        /// <summary>
        /// 数据发送和接收
        /// </summary>
        /// <param name="send">发送数据Byte</param>
        /// <param name="receive">接收数据Byte</param>
        /// <returns></returns>
        private bool SendAndReceive(byte[] send,ref byte[] receive)
        {
            Monitor.Enter(obj);
            byte[] buffer = new byte[1024];
            //内存缓存操作
            MemoryStream stream= new MemoryStream();
            try
            {
                //数据发送
                socket.Send(send,send.Length,SocketFlags.None);
                int timer = 0;
                while (true)
                {
                    Thread.Sleep(SleepTime);
                    if (socket.Available > 0) //判断数据是否在缓冲区有排队
                    {
                        try
                        {
                            //读取数据
                            int count = socket.Receive(buffer,SocketFlags.None);
                            //读取的数据放到Stream流中
                            //可以累积存储所有数据，避免多次处理零散的 byte[]。
                            //后续可以直接操作流（如解析、保存到文件、转字符串等），而无需手动拼接 byte[]
                            stream.Write(buffer,0,count);
                        }
                        catch (Exception)
                        {

                            break;
                        }
                    }
                    else
                    {
                        timer++;
                        if (stream.Length > 0)
                        {
                            break ;
                        }
                        else if(timer > MaxWriteTimes)
                        {
                            return false ;
                        }
                    }

                }
                receive = stream.ToArray();
                return true;
            }
            catch (Exception)
            {

                return false;
            }
            finally
            {
                Monitor.Exit(obj);
            }

        }
        /// <summary>
        /// 读取输入线圈-0x01
        /// </summary>
        /// <param name="strat"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public byte[] ReadoutputCoil(ushort start,ushort length)
        {
            //事务处理 + 协议标识 + 长度 + 单元标识 + 功能码 + 起始线圈地址 + 线圈长度
            ByteArray byteArrey = new ByteArray();

            //1.事务处理 + 协议标识
            byteArrey.Add(0x00, 0x00, 0x00, 0x00);
            //2.长度 + 单元标识  + 功能码
            byteArrey.Add(0x00, 0x06, slaveId, 0x01);
            //3.起始线圈地址 
            byteArrey.Add(start);
            //4.线圈长度
            byteArrey.Add(length);
            byte[] receive = null;
            int bytelength=length % 8 ==0 ? length / 8 : length / 8 + 1; //计算字节长度

            //5.发送并接收报文
            if (SendAndReceive(byteArrey.array,ref receive))
            {
                //6.验证报文
                if (receive.Length == bytelength + 9)
                {
                    if (receive[16] ==slaveId && receive[7] == 0x01 && receive[8]==bytelength)
                    {
                        //7.返回数据
                        byte[] data = new byte[bytelength];
                        Array.Copy(receive, 9, data, 0, bytelength);
                        return data;
                    }
                    else
                    {
                        throw new Exception("功能码错误");
                    }
                }
            }
            return null;

        }

        /// <summary>
        /// 读取输入线圈-0x02
        /// </summary>
        /// <param name="strat"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public byte[] ReadoutputCoils(ushort start, ushort length)
        {
            //事务处理 + 协议标识 + 长度 + 单元标识 + 功能码 + 起始线圈地址 + 线圈长度
            ByteArray byteArrey = new ByteArray();

            //1.事务处理 + 协议标识
            byteArrey.Add(0x00, 0x00, 0x00, 0x00);
            //2.长度 + 单元标识  + 功能码
            byteArrey.Add(0x00, 0x06, slaveId, 0x02);
            //3.起始线圈地址 
            byteArrey.Add(start);
            //4.线圈长度
            byteArrey.Add(length);
            byte[] receive = null;
            int bytelength = length % 8 == 0 ? length / 8 : length / 8 + 1; //计算字节长度

            //5.发送并接收报文
            if (SendAndReceive(byteArrey.array, ref receive))
            {
                //6.验证报文
                if (receive.Length == bytelength + 9)
                {
                    if (receive[16] == slaveId && receive[7] == 0x01 && receive[8] == bytelength)
                    {
                        //7.返回数据
                        byte[] data = new byte[bytelength];
                        Array.Copy(receive, 9, data, 0, bytelength);
                        return data;
                    }
                    else
                    {
                        throw new Exception("功能码错误");
                    }
                }
            }
            return null;

        }

        /// <summary>
        /// 读取输出寄存器-0x03
        /// </summary>
        /// <param name="start"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public byte[] ReadoutPutRegisters(ushort start,ushort length)
        {
            ByteArray byteArray = new ByteArray();
            byteArray.Add(0x00,0x00,0x00,0x00);
            byteArray.Add(0x00, 0x06, slaveId, 0x03);
            byteArray.Add(start);
            byteArray.Add(length);
            byte[] receive = null;
            int bytelength = length * 2; //计算字节长度
            try
            {
                if(SendAndReceive(byteArray.array,ref receive))
                {
                    if (receive.Length == bytelength + 9)
                    {
                        if (receive[16] == slaveId && receive[7] == 0x03 && receive[8] == bytelength)
                        {
                            byte[] data = new byte[bytelength];
                            Array.Copy(receive, 9, data, 0, bytelength);
                            return data;
                        }
                        else
                        {
                            throw new Exception("功能码错误");
                        }
                    }
                }
            }
            catch (Exception)
            {

                return null;
            }
            return null;
        }

        /// <summary>
        /// 读取输入寄存器-0x04
        /// </summary>
        /// <param name="start"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public byte[] ReadInPutRegisters(ushort start, ushort length)
        {
            ByteArray byteArray = new ByteArray();
            byteArray.Add(0x00, 0x00, 0x00, 0x00);
            byteArray.Add(0x00, 0x06, slaveId, 0x04);
            byteArray.Add(start);
            byteArray.Add(length);
            byte[] receive = null;
            int bytelength = length * 2; //计算字节长度
            try
            {
                if (SendAndReceive(byteArray.array, ref receive))
                {
                    if (receive.Length == bytelength + 9)
                    {
                        if (receive[16] == slaveId && receive[7] == 0x04 && receive[8] == bytelength)
                        {
                            byte[] data = new byte[bytelength];
                            Array.Copy(receive, 9, data, 0, bytelength);
                            return data;
                        }
                        else
                        {
                            throw new Exception("功能码错误");
                        }
                    }
                }
            }
            catch (Exception)
            {

                return null;
            }
            return null;
        }

        /// <summary>
        ///设置单个线圈-0x05
        /// </summary>
        /// <param name="start"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool SetSingleCoils(ushort start,bool value)
        {
            ByteArray byteArray=new ByteArray();
            byteArray.Add(0x00,0x00,0x00,0x00);
            byteArray.Add(0x00,0x06,slaveId,0x05);
            byteArray.Add(start);
            byteArray.Add(value ? (byte)0xFF : (byte)0x00, 0x00);//写入的值1-0xFF00 0-0X0000
            byte[] receive = null;
            if (SendAndReceive(byteArray.array,ref receive))
            {
                if (receive.Length == 12)
                {
                    return ByteArrayEquals(byteArray.array, receive);
                }
            }
            return false;
        }

        /// <summary>
        /// 设置单个寄存器-0x06
        /// </summary>
        /// <param name="start"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool SetSingleRegister(ushort start, byte[] value)
        {
            ByteArray byteArray=new ByteArray();
            byteArray.Add(0x00, 0x00, 0x00, 0x00);
            byteArray.Add(0x00, 0x06, slaveId, 0x06);
            byteArray.Add(start);
            byteArray.Add(value);
            byte[] receive = null;
            if (SendAndReceive(byteArray.array, ref receive))
            {
                if (receive.Length == 12)
                {
                    return ByteArayEquals(byteArray.array, receive);
                }
            }
            return false;
        } 
        public bool SetSingleRegister(ushort start, ushort value)
        {
           return SetSingleRegister(start,BitConverter.GetBytes(value).Reverse().ToArray());
        }
        public bool SetSingleRegister(ushort start, short value)
        {
            return SetSingleRegister(start, BitConverter.GetBytes(value).Reverse().ToArray());
        }

        /// <summary>
        /// 设置多个线圈-0x0F
        /// </summary>
        /// <param name="start"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool SetMultiCoils(ushort start, bool[] value)
        {
            //事务处理+协议标识+长度+单元标识+功能码+起始线圈地址+线圈数量+字节计数+字节数据
            ByteArray byteArray = new ByteArray();
            byte[] setArray = ByteArrayFromBoolArray(value);
            byteArray.Add(0x00, 0x00, 0x00, 0x00);//事务处理+协议标识
            byteArray.Add((short)(7 + setArray.Length));//长度
            byteArray.Add(slaveId, 0x0F);//单元标识符+功能码
            byteArray.Add(start);//起始线圈地址
            byteArray.Add((short)value.Length);//线圈数量
            byteArray.Add((short)setArray.Length);//字节计数
            byteArray.Add(setArray);//字节数据

            byte[] receive = null;
            if (SendAndReceive(byteArray.array, ref receive))
            {
                //验证报文
                byte[] send = new byte[12];
                Array.Copy(byteArray.array,0,send,0, send.Length);
                send[4] = 0x00;
                send[5] = 0x06;
                return ByteArrayEquals(send, receive);
            }
            return false;
        }

        /// <summary>
        /// 设置多个寄存器-0x10
        /// </summary>
        /// <param name="start"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public bool SetMultiRegisters(ushort start, byte[] values)
        {
            if(values == null || values.Length==0 || values.Length % 2 == 1)
            {
                return false;
            }
            ByteArray byteArray = new ByteArray();
            byteArray.Add(0x00, 0x00, 0x00, 0x00);//事务处理+协议标识
            byteArray.Add((short)(7 + values.Length));//长度
            byteArray.Add(slaveId, 0x10);//单元标识符+功能码
            byteArray.Add(start);//起始寄存器地址
            byteArray.Add((short)(values.Length / 2));//寄存器数量
            byteArray.Add((byte)values.Length);//字节计数
            byteArray.Add(values);//字节数据
            byte[] receive = null;
            if (SendAndReceive(byteArray.array, ref receive))
            {
                //验证报文
                byte[] send = new byte[12];
                Array.Copy(byteArray.array, 0, send, 0, send.Length);
                send[4] = 0x00;
                send[5] = 0x06;
                return ByteArrayEquals(send, receive);
            }
            return false;
        }

        /// <summary>
        /// 正则表达式检查IP地址是否合法
        /// </summary>
        /// <param name="ip"></param>
        /// <returns></returns>
        public bool CheckIP(string ip )
        {
            string pattern= @"^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$";
            //用于检查字符串是否匹配指定的正则表达式模式
            return Regex.IsMatch(ip, pattern);
        }

        /// <summary>
        /// 比较两个字节数组是否相等用于判断设置单个线圈是否成功
        /// </summary>
        /// <param name="b1"></param>
        /// <param name="b2"></param>
        /// <returns></returns>
        public bool ByteArrayEquals(byte[] b1, byte[] b2)
        {
            if(b1==null || b2 == null)
            {
                return false;
            }
            return b1.SequenceEqual(b2);
        }

        /// <summary>
        /// 将一个bool数组转换为一个字节数组
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public byte[] ByteArrayFromBoolArray(bool[] value)
        {
            int bytelength= value.Length % 8 == 0 ? value.Length / 8 : value.Length / 8 + 1; //计算字节长度
            byte[] result = new byte[bytelength];
            for (int i = 0; i < result.Length; i++)
            {
                int total = value.Length < 8 * (i + 1) ? value.Length - 8 * i : 8;
                for (int j = 0; j < total; j++)
                {
                    result[i] = SetBitValue(result[i], j, value[8 * i + j]);
                }
            }
            return result;
        }

        /// <summary>
        /// 将字节的某一位置1
        /// </summary>
        /// <param name="src"></param>
        /// <param name="bit"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public byte SetBitValue(byte src, int bit, bool value)
        {
            return value ? (byte)(src | (byte)Math.Pow(2, bit)) : (byte)(src & ~(byte)Math.Pow(2, bit));
        }
    }
}
